﻿using Microsoft.WindowsAzure.Storage.Blob;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.IO
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
    public class AzureStream : System.IO.Stream
    {
        #region Public Members

        public AzureFileSystem AzureFileSystem { get; set; }
        public CloudBlockBlob CloudBlob { get; set; }

        #region CanRead

        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region CanSeek

        public override bool CanSeek
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region CanWrite

        public override bool CanWrite
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region Length

        private long? _Length;
        public override long Length
        {
            get
            {
                return _Length.Value;
            }
        }

        #endregion
        #region Position

        private long _Position = 0;
        public override long Position
        {
            get
            {
                return _Position;
            }
            set
            {
                _Position = value;
            }
        }

        #endregion

        #region Dispose

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.AzureFileSystem = null;
            }
            base.Dispose(disposing);
        }

        #endregion
        #region Flush

        public override void Flush()
        {
            var items = GetBlockIdsAsync(CancellationToken.None).Result;
            if (items.Count > 0)
                this.CloudBlob.PutBlockList(items);
        }

        #endregion
        #region FlushAsync

        public override async Task FlushAsync(CancellationToken cancellationToken)
        {
            var items = await GetBlockIdsAsync(cancellationToken).ConfigureAwait(false);
            if (items.Count > 0)
                await this.CloudBlob.PutBlockListAsync(items, cancellationToken).ConfigureAwait(false);
        }

        #endregion
        #region Read

        public override int Read(byte[] buffer, int offset, int count)
        {
            int length;

            if ((this.Position < _Length) && (count > 0))
            {
                length = this.CloudBlob.DownloadRangeToByteArray(buffer, offset, this.Position, count);
                this.Position += length;
                return length;
            }
            return 0;
        }

        #endregion
        #region ReadAsync

        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            int length;

            if ((this.Position < _Length) && (count > 0))
            {
                length = await this.CloudBlob.DownloadRangeToByteArrayAsync(buffer, offset, this.Position, count, cancellationToken).ConfigureAwait(false);
                this.Position += length;
                return length;
            }
            return 0;
        }

        #endregion
        #region Seek

        public override long Seek(long offset, System.IO.SeekOrigin origin)
        {
            switch (origin)
            {
                case System.IO.SeekOrigin.Begin:
                    this.Position = offset;
                    break;
                case System.IO.SeekOrigin.Current:
                    this.Position += offset;
                    break;
                case System.IO.SeekOrigin.End:
                    this.Position = this.Length - offset;
                    break;
                default:
                    break;
            }
            return this.Position;
        }

        #endregion
        #region SetLength

        public override void SetLength(long value)
        {
            _Length = value;
        }

        #endregion
        #region Write

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (count > 0)
            {
                UploadFromByteArray(buffer, offset, this.Position, count);
                this.Position += count;
                if ((_Length == null) || (_Length < this.Position))
                    SetLength(this.Position);
            }
        }

        #endregion
        #region WriteAsync

        public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (count > 0)
            {
                await UploadFromByteArrayAsync(buffer, offset, this.Position, count, cancellationToken).ConfigureAwait(false);
                this.Position += count;
                if ((_Length == null) || (_Length < this.Position))
                    SetLength(this.Position);
            }
        }

        #endregion

        #endregion
        #region Private Members

        #region GetBlockIdsAsync

        private async Task<List<string>> GetBlockIdsAsync(CancellationToken cancellationToken)
        {
            var items = new List<string>();
            var listItems = await this.CloudBlob.DownloadBlockListAsync(BlockListingFilter.Uncommitted,
                Microsoft.WindowsAzure.Storage.AccessCondition.GenerateEmptyCondition(),
                new BlobRequestOptions(),
                new Microsoft.WindowsAzure.Storage.OperationContext(),
                cancellationToken).ConfigureAwait(false);
            foreach (var listItem in listItems)
                items.Add(listItem.Name);
            return items;
        }

        #endregion
        #region GetNewBlockId

        private string GetNewBlockId(long position, int bufferSize)
        {
            var blockIndex = (int)(position / bufferSize);
            var id = blockIndex.ToString("00000", System.Globalization.CultureInfo.InvariantCulture);
            return System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(id));
        }

        #endregion
        #region UploadFromByteArray

        private void UploadFromByteArray(byte[] buffer, int offset, long position, int count)
        {
            var blockId = GetNewBlockId(position, count);
            using (var bufferStream = new System.IO.MemoryStream(buffer, offset, count))
            {
                this.CloudBlob.PutBlock(blockId, bufferStream, null);
            }
        }

        #endregion
        #region UploadFromByteArrayAsync

        private async Task UploadFromByteArrayAsync(byte[] buffer, int offset, long position, int count, CancellationToken cancellationToken)
        {
            if (position == 0)
                await this.CloudBlob.DeleteIfExistsAsync(cancellationToken).ConfigureAwait(false);
            var blockId = GetNewBlockId(position, count);
            using (var bufferStream = new System.IO.MemoryStream(buffer, offset, count))
            {
                await this.CloudBlob.PutBlockAsync(blockId, bufferStream, null, cancellationToken).ConfigureAwait(false);
            }
        }

        #endregion

        #endregion
    }
}
